
#include <string.h>

#include "config_db.h"

/**
 * @brief init a config_db
 * open the db file (if does not exist thus create this file) and
 * may CREATE TABLE config_var and TABLE domain_control if they do
 * not exist.
 * @example
 * sqlite3 *config_db;
 * init_config_db("./jinshu_config.db", &config_db);
 * @param config_db_path sqlite3 db file path
 * @param config_db (OUT) sqlite3 database pp, NOTE **
 * @return int =0 ok =-1 error
 */
int init_config_db(unsigned char *config_db_path, sqlite3 **config_db_pp)
{
    int rc;
    unsigned char *sql;
    rc = sqlite3_open(config_db_path, config_db_pp);
    if (rc != SQLITE_OK)
        return -1;
    sqlite3 *config_db = *config_db_pp;
    sql = "CREATE TABLE IF NOT EXISTS config_var (key text primary key, var text);";
    rc = sqlite3_exec(config_db, sql, NULL, NULL, NULL);
    if (rc != SQLITE_OK)
        return -1;

    sql = "insert or ignore into config_var (key, var) values ('name', 'jinshu');"
          "insert or ignore into config_var (key, var) values ('version', '0.1.0');"
          "insert or ignore into config_var (key, var) values ('log_file_path', './jinshu.log');"
          "insert or ignore into config_var (key, var) values ('hostname', 'localhost');"     // @hostname.hostdomain
          "insert or ignore into config_var (key, var) values ('hostdomain', 'localdomain');" // @hostdomain
          "insert or ignore into config_var (key, var) values ('ipv4_address', '127.0.0.1');"
          "insert or ignore into config_var (key, var) values ('ipv6_address', '::1');"
          "insert or ignore into config_var (key, var) values ('port', '25');"
          "insert or ignore into config_var (key, var) values ('handle_threads_num', '2');"
          "insert or ignore into config_var (key, var) values ('accept_threads_num', '2');"
          "insert or ignore into config_var (key, var) values ('output_threads_num', '1');"
          "insert or ignore into config_var (key, var) values ('max_mail_size', '33554432');";
    rc = sqlite3_exec(config_db, sql, NULL, NULL, NULL);
    if (rc != SQLITE_OK)
        return -1;
    sql = "CREATE TABLE IF NOT EXISTS domain_control (type int not null, user text, domain text not null, primary key(user, domain));";
    rc = sqlite3_exec(config_db, sql, NULL, NULL, NULL);
    if (rc != SQLITE_OK)
        return -1;

    return 0;
}

int close_config_db(sqlite3 *config_db)
{
    if (sqlite3_close_v2(config_db) != SQLITE_OK)
        return -1;
    else
        return 0;
}

/**
 * @brief Get the config var by key
 * @param config_db sqlite3 db pointer
 * @param key MUST have zero terminator
 * @param res (OUT)
 * @param res_max_len max length of res
 * @return int =0 ok =-1 error
 */
int get_config_var(sqlite3 *config_db, unsigned char *key, unsigned char *res, int res_max_len)
{
    if (strlen(key) > 64)
        return -1;
    int rc;
    unsigned char sql[128] = "SELECT var FROM config_var WHERE key=?1;";
    sqlite3_stmt *stmt;
    rc = sqlite3_prepare_v2(config_db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK)
    {
        sqlite3_finalize(stmt);
        return -1;
    }
    rc = sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
    if (rc != SQLITE_OK)
    {
        sqlite3_finalize(stmt);
        return -1;
    }
    rc = sqlite3_step(stmt);
    if (rc == SQLITE_ROW)
    {
        const unsigned char *r = sqlite3_column_text(stmt, 0);
        int rl = sqlite3_column_bytes(stmt, 0);
        if (rl >= res_max_len)
        {
            sqlite3_finalize(stmt);
            return -1;
        }
        else
        {
            strncpy(res, r, rl);
            res[rl] = '\0';
            sqlite3_finalize(stmt);
        }
    }
    else if (rc == SQLITE_DONE)
    {
        res[0] = '\0';
        sqlite3_finalize(stmt);
    }
    else
    {
        sqlite3_finalize(stmt);
        return -1;
    }

    return 0;
}

/**
 * @brief Set the config var
 *
 * @param config_db
 * @param key MUST have zero terminator
 * @param var MUST have zero terminator
 * @return int =0 ok -1 error
 */
int set_config_var(sqlite3 *config_db, unsigned char *key, unsigned char *var)
{
    if (strlen(key) > 64 || strlen(var) > 64)
        return -1;
    int rc;
    unsigned char sql[192] = "INSERT or REPLACE INTO config_var (key, var) VALUES (?1, ?2);";
    sqlite3_stmt *stmt;
    rc = sqlite3_prepare_v2(config_db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK)
    {
        sqlite3_finalize(stmt);
        return -1;
    }
    rc = sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
    if (rc != SQLITE_OK)
    {
        sqlite3_finalize(stmt);
        return -1;
    }
    rc = sqlite3_bind_text(stmt, 2, var, -1, SQLITE_TRANSIENT);
    if (rc != SQLITE_OK)
    {
        sqlite3_finalize(stmt);
        return -1;
    }
    rc = sqlite3_step(stmt);
    if (rc != SQLITE_DONE)
    {
        sqlite3_finalize(stmt);
        return -1;
    }

    sqlite3_finalize(stmt);
    return 0;
}

/**
 * @param config_db sqlite3 database pointer
 * @param key MUST have zero terminator
 * @return int =0 ok -1 error
 */
int del_config_var(sqlite3 *config_db, unsigned char *key)
{
    if (strlen(key) > 64)
        return -1;
    int rc;
    unsigned char sql[192] = "DELETE FROM config_var WHERE key=?1;";
    sqlite3_stmt *stmt;
    rc = sqlite3_prepare_v2(config_db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK)
    {
        sqlite3_finalize(stmt);
        return -1;
    }
    rc = sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
    if (rc != SQLITE_OK)
    {
        sqlite3_finalize(stmt);
        return -1;
    }
    rc = sqlite3_step(stmt);
    if (rc != SQLITE_DONE)
    {
        sqlite3_finalize(stmt);
        return -1;
    }

    sqlite3_finalize(stmt);
    return 0;
}

/**
 * @brief Get domain control entry
 *
 * @param config_db
 * @param user can be NULL, MUST have zero terminator
 * @param domain MUST have zero terminator
 * @return domain_ctrl_type_t
 */
domain_ctrl_type_t get_domain_ctrl(sqlite3 *config_db, unsigned char *user, unsigned char *domain)
{
    int rc;
    unsigned char sql[128] = {0};
    if (domain == NULL)
        return DOMAIN_UNKNOW;
    else if (user == NULL)
        strcpy(sql, "SELECT type FROM domain_control WHERE user IS NULL AND domain=?1;");
    else
        strcpy(sql, "SELECT type FROM domain_control WHERE user=?1 AND domain=?2;");
    sqlite3_stmt *stmt;
    rc = sqlite3_prepare_v2(config_db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK)
    {
        sqlite3_finalize(stmt);
        return -1;
    }
    if (user == NULL)
        rc = sqlite3_bind_text(stmt, 1, domain, -1, SQLITE_TRANSIENT);
    else
    {
        rc = sqlite3_bind_text(stmt, 1, user, -1, SQLITE_TRANSIENT);
        rc &= sqlite3_bind_text(stmt, 2, domain, -1, SQLITE_TRANSIENT);
    }
    if (rc != SQLITE_OK)
    {
        sqlite3_finalize(stmt);
        return -1;
    }
    rc = sqlite3_step(stmt);
    if (rc == SQLITE_ROW)
    {
        domain_ctrl_type_t r = sqlite3_column_int(stmt, 0);
        sqlite3_finalize(stmt);
        return r;
    }
    else
    {
        sqlite3_finalize(stmt);
        return DOMAIN_UNKNOW;
    }
}

/**
 * @brief Set the domain ctrl entry
 *
 * @param config_db
 * @param type can NOT be DOMAIN_UNKNOW
 * @param user can be NULL, MUST have zero terminator
 * @param domain MUST have zero terminator
 * @return int =0 ok =-1 error
 */
int set_domain_ctrl(sqlite3 *config_db, domain_ctrl_type_t type, unsigned char *user, unsigned char *domain)
{
    if (type == DOMAIN_UNKNOW)
        return -1;
    int rc;
    unsigned char sql[128] = {0};
    if (domain == NULL)
        return DOMAIN_UNKNOW;
    else if (user == NULL)
        strcpy(sql, "INSERT or REPLACE INTO domain_control (type, domain) VALUES (?1, ?2);");
    else
        strcpy(sql, "INSERT or REPLACE INTO domain_control (type, user, domain) VALUES (?1, ?2, ?3);");
    sqlite3_stmt *stmt;
    rc = sqlite3_prepare_v2(config_db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK)
    {
        sqlite3_finalize(stmt);
        return -1;
    }
    if (user == NULL)
    {
        rc = sqlite3_bind_int(stmt, 1, type);
        rc &= sqlite3_bind_text(stmt, 2, domain, -1, SQLITE_TRANSIENT);
    }
    else
    {
        rc = sqlite3_bind_int(stmt, 1, type);
        rc &= sqlite3_bind_text(stmt, 2, user, -1, SQLITE_TRANSIENT);
        rc &= sqlite3_bind_text(stmt, 3, domain, -1, SQLITE_TRANSIENT);
    }
    if (rc != SQLITE_OK)
    {
        sqlite3_finalize(stmt);
        return -1;
    }
    rc = sqlite3_step(stmt);
    if (rc != SQLITE_DONE)
    {
        sqlite3_finalize(stmt);
        return -1;
    }

    sqlite3_finalize(stmt);
    return 0;
}

/**
 * @brief delete domain ctrl entry
 *
 * @param config_db
 * @param user can be NULL, MUST have zero terminator
 * @param domain MUST have zero terminator
 * @return int =0 ok =-1 error
 */
int del_domain_ctrl(sqlite3 *config_db, unsigned char *user, unsigned char *domain)
{
    int rc;
    unsigned char sql[128] = {0};
    if (domain == NULL)
        return DOMAIN_UNKNOW;
    else if (user == NULL)
        strcpy(sql, "DELETE FROM domain_control WHERE user IS NULL AND domain=?1;");
    else
        strcpy(sql, "DELETE FROM domain_control WHERE user=?1 AND domain=?2;");
    sqlite3_stmt *stmt;
    rc = sqlite3_prepare_v2(config_db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK)
    {
        sqlite3_finalize(stmt);
        return -1;
    }
    if (user == NULL)
    {
        rc = sqlite3_bind_text(stmt, 1, domain, -1, SQLITE_TRANSIENT);
    }
    else
    {
        rc = sqlite3_bind_text(stmt, 1, user, -1, SQLITE_TRANSIENT);
        rc &= sqlite3_bind_text(stmt, 2, domain, -1, SQLITE_TRANSIENT);
    }
    if (rc != SQLITE_OK)
    {
        sqlite3_finalize(stmt);
        return -1;
    }
    rc = sqlite3_step(stmt);
    if (rc != SQLITE_DONE)
    {
        sqlite3_finalize(stmt);
        return -1;
    }

    sqlite3_finalize(stmt);
    return 0;
}
